Tracking: rebased and refactored Cotracker integration#187
Draft
C-Achard wants to merge 82 commits intocy/refactor-fix-traj-plot-y-axis-rebasedfrom
Draft
Tracking: rebased and refactored Cotracker integration#187C-Achard wants to merge 82 commits intocy/refactor-fix-traj-plot-y-axis-rebasedfrom
C-Achard wants to merge 82 commits intocy/refactor-fix-traj-plot-y-axis-rebasedfrom
Conversation
Introduce an in-memory debug recorder and helper functions for collecting diagnostics. Adds install_debug_recorder/get_debug_recorder and InMemoryDebugRecorder (bounded deque, fail-open, no I/O, safe message/exception handling) attached to the 'napari-deeplabcut' logger. Also adds collect_environment_summary, summarize_layer, summarize_viewer, and format_debug_report to produce a redact-safe diagnostic report with environment info, viewer/layer summaries, and recent plugin logs.
Introduce build_debug_report to assemble environment info, viewer summary, and recorder logs into the existing format_debug_report. It accepts a viewer, an optional InMemoryDebugRecorder and a log_limit (default 300), uses recorder.render_text when available, and falls back to a placeholder string. Also adds a top-of-file path comment.
Introduce a new debug text viewer dialog (src/napari_deeplabcut/ui/debug_window.py). Adds DebugTextWindow — a read-only, refreshable, copyable dialog using a text provider callable, with fixed-width font and optional Ctrl/Cmd-C shortcut. Also adds helper providers make_log_text_provider and make_issue_report_provider to adapt InMemoryDebugRecorder and build_debug_report for display. Handles provider exceptions gracefully and provides status feedback when copying to the clipboard.
Install a debug recorder and add a "Generate debug log" help-menu action that opens a DebugTextWindow with recent plugin logs (using make_issue_report_provider/get_debug_recorder). Move and consolidate label_mode/color_mode handlers and related multi-animal checks earlier in the file.
Rename several menu actions to include "napari-dlc" (debug log, tutorial, shortcuts) to make the plugin-specific commands explicit. Change DebugTextWindow instantiation to use self as the parent instead of viewer.window._qt_window to ensure correct parenting. Replace moveCursor call with QTextCursor.MoveOperation.Start and add the QTextCursor import for a reliable cursor move to the start. Minor comment/separator added.
Introduce logger.debug statements across KeypointControls to aid debugging of layer setup, merging, and adoption flows. Log context includes layer name/type, metadata (project, root, paths length), store counts, allowed merges, and keypoint diffs when merging. Also minor refactor to use a local md variable (layer.metadata or {}) and copy it for new_metadata.
Reduce the minimum in-memory log buffer from 10 to 1 (use max(1, int(capacity))) so callers can specify very small capacities. Also normalize _safe_tail output to POSIX-style strings by calling as_posix() on returned Path objects to ensure consistent path formatting across platforms and avoid backslashes on Windows.
Add comprehensive unit tests for the debug UI and recorder utilities. Tests cover DebugTextWindow behaviors (initial load, refresh, copy, shortcut, failure handling) and the text providers (log and issue report). Add tests for InMemoryDebugRecorder (installation idempotency, capture of messages and exceptions, bounded capacity, clear, render_text, and handling unrenderable messages). Also test helper utilities: _safe_tail, collect_environment_summary, summarize_layer, summarize_viewer, and format_debug_report. Uses qtbot for GUI tests and simple fakes/mocks for viewers/layers and recorder.
Extract plugin-related UI setup into _add_plugin_actions and consolidate repeated form builders (_form_dropdown_menus, _form_mode_radio_buttons, _form_color_mode_selector). Move help-menu actions (tutorial, shortcuts, debug log) and global keybinding installation into a single helper and remove duplicated method definitions to reduce code dispersion and improve readability. No functional changes intended.
Introduce a new core.layer_lifecycle package (manager, registry, merge types) to centralize viewer layer lifecycle handling and managed Points runtime attachments. Integrate the new LayerLifecycleManager into _widgets.py (instantiate manager, comment out direct viewer layer signal hookups) and add a debug log in on_insert. The RuntimeRegistry and ManagedPointsRuntime provide weak-ref backed registrations for managed Points layers, while MergeDecision types define merge policy primitives.
Replace KeypointControls' internal _stores usage with LayerLifecycleManager and update lifecycle wiring. KeypointControls now creates LayerLifecycleManager(owner=self), calls attach(), and defers points registration/unregistration and layer queries to the manager (new managed_points_layers/iter_managed_points/managed_points_count/has_managed_points helpers). Deprecated direct _stores access and updated TrailsController and various control paths to use the manager API. LayerLifecycleManager constructor now accepts owner, derives viewer from owner, supports QObject parenting, and adds register_managed_layer wrapper that delegates to register_managed_points_layer (with stricter type checks). Also added iteration helpers and counting convenience methods. Minor registry file: added logger setup.
Replace direct accesses to the private _stores dict with the public layer manager API in tests. Tests now call get_layer_store(...) (controls/keypoint_controls) and use register_managed_layer(...) instead of assigning into _stores, improving encapsulation and avoiding reliance on internal state. Updated test files: e2e/test_layer_coloring.py, e2e/test_overwrite_and_merge.py, e2e/test_points_layers.py, e2e/test_routing_and_provenance.py, and test_widgets.py.
Add unit tests for LayerLifecycleManager and RuntimeRegistry. Tests cover idempotent attach/detach, registering/unregistering managed points, on_insert/on_remove behavior delegating to owner methods, adopting existing layers while skipping already-managed points, resolving inserted layers from event payloads, and manager remapping/refresh calls. Also add RuntimeRegistry tests for register/get/require/unregister roundtrips, duplicate registration errors, items/layers/runtimes consistency, and purging dead weakrefs. Includes small test helpers (dummy viewer/owner/layers).
Major refactor of the layer lifecycle registry and manager to centralize layer liveness resolution and provide observable cleanup/auditing. Key changes: - RuntimeRegistry now tracks entries by stable layer_id (id(layer)) and resolves layer liveness via weakrefs; ManagedPointsRuntime stores layer_id instead of a strong layer reference. - Added diagnostic types: ClearedRegistryEntry, RegistryAuditIssue, RegistryAuditReport, and an audit() API to produce a structured report of live/dead entries and issues. - Added clear_dead_entries() to remove and return dead entries (with optional logging) so reaping is observable instead of implicit/purge-on-access. - Introduced live-focused iterators and accessors: iter_live_items(), iter_live_layers(), iter_live_runtimes(), resolve_live_layer(), get_live_runtime()/require_live_runtime(), get_store()/require_store(), and layer_ids()/contains_layer_id(). - Registry methods now validate runtime.layer_id consistency and fall back to strong storage with a logged error if an object cannot be weak-referenced. - RuntimeRegistry.assert_consistent() now uses the audit report and raises if issues exist. Manager and widget updates: - LayerLifecycleManager exposes new facade methods (resolve_live_layer, get_live_runtime, get_store, require_store, clear_dead_entries, audit_registry) and adapts registration/unregistration to accept layer objects or ids. - Iteration and counting use live-only iterators; register ensures runtime.layer_id matches the actual layer id. - KeypointControls close handler now clears dead entries and logs an audit report if issues are found. Motivation: - Centralize liveness checks and make cleanup explicit and debuggable to avoid silent resource leaks and make lifecycle bugs easier to diagnose.
Refactor and extend tests for LayerLifecycleManager and RuntimeRegistry to match updated runtime API and lifecycle behavior. Tests now import gc and exercise resolve_live_layer/get_live_runtime/get_store/require_store paths, use ManagedPointsRuntime with layer_id, and assert registry iteration helpers (iter_live_items/iter_live_layers/iter_live_runtimes) and layer_ids. Added new tests to verify reaping/clearing of dead entries, audit/reporting of stale entries, and unregister-by-layer-id behavior. Adjusted expectations and renamed several test functions to reflect the new APIs.
Import deprecation helper and mark legacy layer-handling methods as deprecated. Added from napari_deeplabcut.utils.deprecations import deprecated and applied @deprecated to _adopt_existing_layers, _adopt_layer, and on_insert with messages pointing to LayerLifecycleManager / self.layer_manager replacements, clarifying that layer adoption and direct layer management are now handled elsewhere.
Add @deprecated annotations to KeypointControls methods that belong in LayerLifecycleManager: _setup_image_layer, _wire_points_layer, and _setup_points_layer. The decorators include messages indicating these methods are still used by LayerLifecycleManager but should be moved there. No functional logic changes, only deprecation metadata to guide refactoring.
Refactor lifecycle responsibilities by moving image/points setup, wiring, removal and remap logic out of the UI widget and into LayerLifecycleManager. Key changes: - _widgets.py: switched to relative imports, removed many lifecycle implementations and replaced them with deprecated UI-facing wrappers that delegate to layer_manager; preserved small UI completion hooks used by the widget. - core/layer_lifecycle/manager.py: added lifecycle-owned methods (_setup_image_layer, _wire_points_layer, _setup_points_layer, _handle_removed_layer, _remap_frame_indices) and attach_points_layer_runtime to centralize runtime bindings (paste/add wrappers, events, keybindings), plus adoption/insert/remove handling. - core/keypoints.py: KeypointStore now supports weak refs and a resolver-based lifecycle (layer id/resolution), raises LayerUnavailableError when layer is gone, exposes add() (with a temporary _add compat wrapper), and renamed nearest-neighbor helper to find_nearest_neighbors; several safety/fallbacks added. - core/layer_lifecycle/registry.py: added PointsRuntimeResources dataclass to track non-Qt runtime attachments for managed Points layers. - Misc: logging/message tweaks, deprecated annotations for moved methods, and transitional notes to keep UI-specific behavior separated from lifecycle logic. This reorganizes ownership of runtime behavior to make lifecycle management testable and encapsulated while retaining UI-only completion in the widget.
Replace uses of internal helpers with public API (keypoints._add -> keypoints.add; keypoints._find_nearest_neighbors -> keypoints.find_nearest_neighbors) and add missing pytest import. Add tests covering layer resolver behavior (maybe_layer, LayerUnavailableError) and the store.layer setter updating layer_id and keypoints. Also add an assertion to verify store.layer_id matches id(store.layer) in test_store_labels.
Align tests with recent refactors to the layer lifecycle manager and keypoint APIs. Tests now use lightweight fakes and fixtures (FakeStore, immediate QTimer, fake runtime attachment) and a more featureful DummyOwner that exposes MagicMock hooks so assertions check behavior via those hooks instead of internal call lists. Adapted tests to renamed/updated APIs: manager parameter existing -> existing_resources, reap_dead_entries -> clear_dead_entries, and the temporary keypoints wrapper renamed to add; tests also call store.add directly where appropriate. Minor test reorganizations improve determinism and isolate lifecycle orchestration from napari internals.
Centralize lifecycle-owned image and project metadata in LayerLifecycleManager: added ImageMetadata storage, project_path, and convenience accessors (image_root, image_paths, image_name). Moved image metadata update, points→image metadata sync, and project-path caching from KeypointControls into the manager, and updated _setup_image_layer and related flows to use the manager-owned context. KeypointControls retains compatibility shims and deprecated wrappers that delegate to LayerLifecycleManager, and call sites were updated to reference layer_manager.* instead of local fields. Import and metadata helper usage was adjusted accordingly; behavior should be preserved while ownership is moved to the manager.
Add explicit DLC lifecycle metadata propagation and lifecycle handling for image/video layers. - Reader changes: build and attach a standardized dlc metadata dict for images and videos (via a new _build_dlc_layer_meta); infer project context for opened videos and labeled folders and pass dlc_meta into read_images/read_video. Non-DLC media remain supported but are marked as non-session. - IO refactor: read_images now accepts dlc_meta and uses a helper to build image layer kwargs; read_video accepts optional dlc_meta and includes it in returned metadata. - Project paths: add infer_dlc_project_from_labeled_folder, infer_dlc_project_from_video_path, and session_key_from_project_context to better infer session contexts for folders and directly opened videos. - Lifecycle manager: track the active DLC image layer id, determine/accept/reject DLC session image layers, expose active_dlc_image_layer, and add safe helpers for removing layers and setting visibility; ensure conflicting DLC layers are rejected and non-session images are ignored. - Widgets/UX: KeypointControls updated to use layer lifecycle manager to hide/show or remove placeholder layers safely, improved logic to pick visible existing layers, and a safer implementation to move image layers to bottom. Uses manager.active_dlc_image_layer instead of deprecated helper. - API note: find_relevant_image_layer is marked deprecated in favor of LayerLifecycleManager.active_dlc_image_layer. These changes let the plugin reliably identify DLC project/video sessions, attach session metadata to Napari layers, and prevent multiple conflicting DLC sessions from being opened simultaneously.
Detect and merge temporary "config placeholder" Points layers into existing managed points workflows, updating headers, presentation metadata, dropdowns, and UI state, then remove the placeholder layer. KeypointControls: added _is_config_placeholder_points_layer and rewrote _maybe_merge_config_points_layer to use a managed layer as the reference, merge header/bodypart changes, refresh menus defensively, apply colormap/face-color cycles, and remove the placeholder via QTimer. LayerLifecycleManager: made active_dlc_image_layer resolve by identity across viewer.layers (safer than resolve_live_layer) and short-circuit insertion when a placeholder layer is consumed by the widget merge. Tests updated with a helper (mark_as_dlc_session_image) and adjusted assertions to reflect the new merge/move behavior. Also adjusted a deprecation decorator parameter name in core/layers.py.
Add explicit session conflict handling and a UI notification hook. - Introduce a new Signal session_conflict_rejected(str) on LayerLifecycleManager to notify the UI when a DLC labeled-data folder conflicts with the current session. - Change the rejection flow to emit the signal and defer layer removal via QTimer.singleShot(0) instead of removing the layer synchronously (avoids Napari list-insertion/finalization issues). - Expand the rejection message to provide clearer guidance to users about loading annotations vs switching projects. - Wire the new signal into KeypointControls: attach LayerLifecycleManager, connect session_conflict_rejected to a new _on_session_conflict_detected handler, and show a QMessageBox warning when a conflict is detected. - Improve logging when deferred removal fails. This change brings better user feedback when loading several projects.
Wrap widget removal in a safe try/except. In KeypointStore, skip revalidating header metadata when the same live layer is rebound (check by id) to avoid repeated expensive reads and add a debug log. In LayerLifecycleManager add a post-remove coalescing QTimer and flush handler to batch UI refreshes after layer removals, make _update_image_meta_from_layer return a bool indicating whether the authoritative image context changed, and only sync points layers when the context actually changed. Also adjust layer setup/remap logic to only remap non-image layers when necessary and improve related logging. Overall these changes reduce unnecessary work, prevent transient exceptions, and coalesce UI updates during layer changes.
Introduce a lightweight log_timing contextmanager for scoped performance measurements and a NAPARI_DLC_LOG_TIMING flag to control sampling. Instrument several lifecycle and UI hotspots (Points/Image/Tracks removal, viewer.layers.remove, post-remove refresh, and active-layer changes) to emit timing logs when enabled. Enhance the InMemoryDebugRecorder snapshot formatting to include milli-second precision timestamps, relative elapsed ms per record, and guard the empty-record case. These changes aid low-overhead debugging and performance investigation.
Replace hardcoded HDF5 key "df_with_missing" with the canonical DLC_CANONICAL_H5_KEY constant when reading the stored dataframe in compute_overwrite_report_for_extracted_labels_row. Also consolidate the DLC_CANONICAL_H5_KEY import (removed duplicate) and keep the existing fallback to read_hdf without a key for backward compatibility.
Add optional is_ma_project flag to header-shaping helpers and use DLC config to resolve project mode before writing. Introduce _resolve_multianimalproject_for_write which searches project/config candidates (pts_meta.project, output parent, pts_meta.root) for a multianimalproject setting and returns True/False/None. Pass the resolved value into restore_dlc_on_disk_header_shape so single-animal vs multi-animal canonicalization follows explicit config when available. Also adjust docstrings and minor whitespace cleanup.
Rebuild columns from existing self.columns when replacing the scorer rather than relying on _canonical_4. Each column element is stringified and the scorer is placed as the first entry; columns with varying lengths (including empty ones) are handled gracefully. The model now preserves self.names instead of overwriting them with a fixed ['scorer','individuals','bodyparts','coords'], avoiding loss of custom column name layouts.
KeypointStore: add _clear_stale_selection_if_off_frame to purge selected points that do not belong to the current frame and call it at the start of add(). Track whether layer data/properties actually changed and only trigger layer.events.query_next_frame() or next_keypoint() when a real change occurred to avoid unnecessary frame/keypoint advances. LayerLifecycleManager: tighten header individuals check by using any(str(ind) != "" for ind in inds) to more robustly detect non-empty individual IDs.
Extract dropdown show/hide logic into a new _sync_dropdown_visibility method and call it when menus are created and when the active layer changes. The helper uses the layer->menu mapping to show the menu for the active Points layer and hide all others. Also adds a TODO suggesting consolidation to a single synced DropdownMenu instance and minor whitespace cleanup.
Return early from guarantee_multiindex_rows when the DataFrame has no rows to avoid indexing into df.index[0]. Added test_guarantee_multiindex_rows_empty_df_is_noop to assert an empty DataFrame remains unchanged (length 0 and not a MultiIndex).
Introduce widget-owned timer management to replace ad-hoc QTimer.singleShot usage. Add _timers and _temp_timers, plus _schedule_once and _single_shot_owned helpers to coalesce named single-shot callbacks and track transient timers. Connect _cleanup_timers to destroyed to defensively stop/delete timers during teardown and avoid RuntimeError from torn-down C++ objects. Use the new scheduling API for startup refresh/dock/status tasks and deferred recolor flushing to improve robustness and prevent stray timers from firing after widget destruction.
Introduce an experimental point-tracking subsystem: adds a TrackingControls UI, a background TrackingWorker, and core data/models for registering and running trackers. Implements concrete modules under tracking/core (data and abstract TrackingModel with registry), a Qt widget under tracking/_widgets.py, and a worker implementation under tracking/_worker.py. Includes tests (fixtures, widget and worker tests) and a README documenting usage and keybindings; tests register a DummyTracker for unit testing. Adds "torch" to pyproject.toml and updates tox.ini to support the new tracking tests. Co-Authored-By: Arash Sal Moslehian <57039957+arashsm79@users.noreply.github.com>
Co-Authored-By: Arash Sal Moslehian <57039957+arashsm79@users.noreply.github.com>
Introduce configurable tracking shortcuts and only enable them when the tracking widget is visible. Add TRACKING_SHORTCUTS_ENABLED (env NAPARI_DLC_TRACKING_SHORTCUTS_ENABLED, default enabled) to settings, centralize tracking key definitions in config/keybinds.py, and have iter_shortcuts yield tracking shortcuts only when enabled. Update TrackingControls to import those key configs, set an objectName/property for dock detection, and guard bound key callbacks so they only act when the tracking dock is open/visible. Enhance Shortcuts dialog to recognize the new "tracking-points-layer" scope and to hide/disable shortcuts when the tracking widget isn't open. Also update imports (KeypointStore path) and remove duplicate dataclass/keybind definitions.
Introduce an identity-preserving tracking data pipeline and create separate tracking result layers. data.py: add tracking metadata constants, convert TrackingWorkerData/Output to use DataFrame for per-query features, and add helpers (coerce_features_df, add_query_identity_columns, expand_query_features_over_time, build_tracking_result_metadata, is_tracking_result_layer) to preserve query identity and expand features over time. models.py: update Cotracker3 to avoid mutating inputs, correctly handle x/y ordering, visibility shapes, time-reversal for backward tracking, and produce flattened keypoints plus expanded per-point feature rows. _widgets.py: add UI helpers to seed query points/features, build a non-destructive tracking result Points layer (with metadata and visual tweaks), wire those into the tracking flow, improve error handling, and select the created layer in the viewer. Overall these changes ensure semantic identity of query points is preserved through inference and that results are stored in a dedicated, annotated layer.
Add helpers to place all test keypoints on a specific frame and to drive the widget reference frame (_put_all_points_on_frame, _set_current_tracking_frame). Update tests (test_backward_track, test_bothway_track) to use these helpers instead of directly mutating viewer.dims, and add assertions verifying the tracking request payload: correct reference_frame_index, sliced video length, keypoints rebased to local frame 0, original keypoint feature columns (id/name) preserved, and new tracking identity columns (tracking_query_index, tracking_query_frame) populated. Also ensure both-way tracking uses the same seed frame and that forward==reference only triggers backward tracking.
Switches several signal/slot connections to explicit Qt.QueuedConnection to ensure safe cross-thread delivery and replaces tuple/object-typed signals with primitive signatures. In TrackingControls: trackingRequested now uses Signal(object); worker connections use queued delivery; added _on_worker_started/_on_worker_finished/_on_worker_progress slots, a _request_worker_stop wrapper, and a _debug_thread helper for logging. In TrackingWorker: progress is now Signal(int,int), trackingFinished is Signal(object), track is annotated with Slot(object), and progress.emit now emits two ints; added thread debug logging. These changes improve thread-safety, clarity of signal payloads, and progress reporting.
Update tracking worker and UI to improve stop handling, logging, and error reporting. - TrackingWorker: replace boolean stop flag with threading.Event, add request_stop(), use self.thread for logging, emit trackingStarted/trackingStopped/trackingFinished and finished in appropriate places, avoid QCoreApplication.processEvents, and guard torch.cuda.empty_cache. Ensure early return when stop requested. - Tests: adapt progress signal handling to accept (current, total) args. - Widgets: use self.thread attribute in debug logs, derive reference frame index from returned features when available, catch ValueError from seed point queries and show a napari warning instead of crashing, plus small formatting fixes. These changes make worker shutdown safer across threads, improve diagnostics, and prevent main-thread operations from being performed in the worker.
Switch the CoTracker backend to the offline model and refactor run() to perform whole-clip inference. Inputs.video is converted and permuted to (1, T, C, H, W), queries are batched to (1, K, 3), inference is executed under torch.inference_mode, progress callbacks updated, and a RuntimeError is raised if no predictions are returned. Also add early-stop handling (return None). Additionally, fix visibility array handling in expand_query_features_over_time by squeezing a leading singleton channel dimension when vis.ndim == 3 and vis.shape[0] == 1.
Tweak tracking UI appearance and fix a threading/logging bug. - tracking/_widgets.py: add TODO about slider sync; change tracking layer visuals to use symbol="cross", opacity=0.85, and border_color="green" to better distinguish results. - tracking/ui/worker.py: fix logging call by using self.thread instead of self.thread() to avoid a mistaken call in the thread information.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Use a lifecycle manager in TrackingControls and tighten viewer typing; update test helper to locate the TrackingControls dock by isinstance and add TYPE_CHECKING napari types. Fix a TYPE_CHECKING import in tracking models (correct TrackingModel import). Replace top-level torch import in the tracking worker with a lazy import and a clear ImportError message advising how to install the tracking extras, and improve CUDA cache cleanup logging. These changes reduce heavy top-level imports, improve typing correctness, and make missing-dependency errors clearer.
b1d7422 to
57653c0
Compare
Collaborator
|
Good that you've rebased this. Had a brief look again in the code to refresh myself with the latest changes and current status. Let me know when you need a more thorough review of the implementation or any feedback for further directions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebase of #155, updated to integrate with the latest refactor.
Further update from #128
Current ongoing work:
Done
Missing
-> See #190 for the merge UI